//
// request_handler.cpp
// ~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2008 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#include "asio.h"
#include "request_handler.hpp"
#include <fstream>
#include <sstream>
#include <string>
#include <boost/lexical_cast.hpp>
#include "mime_types.hpp"
#include "reply.hpp"
#include "request.hpp"

namespace http {
namespace server {

struct VFS
{
	std::string name;
	std::vector<VFS*> nodes;
	void* tag;

	~VFS()
	{
		for (size_t i=0; i<nodes.size(); ++i)
			delete nodes[i];
	}

	typedef std::vector<VFS*>::iterator iterator;
	struct comp
	{
		bool operator()(VFS* a, VFS* b)
		{
			return a->name < b->name;
		}
		bool operator()(VFS* x, const std::string& name)
		{
			return x->name < name;
		}
		bool operator()(const std::string& name, VFS* x)
		{
			return name < x->name;
		}
	};

	VFS* lookup(const std::string& item)
	{
		iterator it=lower_bound(
			nodes.begin(),nodes.end(), 
			item, comp());

		if (it==nodes.end() || (*it)->name!=item)
			return NULL;

		return *it;
	}

	VFS* lookup_new(const std::string& item)
	{
		VFS* vfs=lookup(item);
		if (vfs) return vfs;

		vfs=new VFS;
		vfs->name=item;
		vfs->tag=NULL;
		nodes.push_back(vfs);

		comp less;
		for (size_t i=nodes.size()-1; i>0; --i)
			if (less(nodes[i], nodes[i-1]))
				std::swap(nodes[i], nodes[i-1]);
		return vfs;
	}
};

const static char* ArchiveRoot="ArchiveContent";

request_handler::request_handler(const std::string& doc_root)
  : doc_root_(doc_root)
{
	vfs_=new VFS;
	vfs_->name=ArchiveRoot;
	vfs_->tag=NULL;
}

request_handler::~request_handler()
{
	delete vfs_;
}

void split(const std::string& path, std::vector<std::string>& components)
{
	size_t i,j;
	components.clear();
	for (i=j=0; i<=path.size(); ++i)
		if (i==path.size() || path[i]=='/' || path[i]=='\\')
		{
			std::string s=path.substr(j, i-j);
			j=i+1;
			if (s.empty() || s==".") continue;
			else if (s=="..") {components.pop_back(); continue;}
			components.push_back(s);
		}
}

void request_handler::add_virtual_resource(const std::string& path, void* tag)
{
	std::vector<std::string> components;
	split(path, components);

	VFS* vfs=vfs_;
	for (size_t i=0; i<components.size(); ++i)
		vfs=vfs->lookup_new(components[i]);
	vfs->tag=tag;
}

void request_handler::serve_directory(std::string& out, VFS* node)
{
	size_t i;
	std::vector<VFS*> files, subdirs;
	for (i=0; i<node->nodes.size(); ++i)
	{
		VFS* item=node->nodes[i];
		if (item->nodes.empty()) files.push_back(item);
		else subdirs.push_back(item);
	}

	out+="{\"name\":\"";
	out+=node->name;
	out+="\", \"SubDirectories\":[";
	for (i=0; i<subdirs.size(); ++i)
	{
		serve_directory(out, subdirs[i]);
		if (i<subdirs.size()-1)
			out+=',';
	}
	out+="], \"files\":[";
	for (i=0; i<files.size(); ++i)
	{
		out+='\"';
		out+=files[i]->name;
		out+='\"';
		if (i<files.size()-1)
			out+=',';
	}
	out+="]}";
}

void request_handler::handle_request(const request& req, reply& rep)
{
  // Decode url to path.
  std::string request_path;
  if (!url_decode(req.uri, request_path))
  {
    rep = reply::stock_reply(reply::bad_request);
    return;
  }

  puts(request_path.c_str());
  // Request path must be absolute and not contain "..".
  if (request_path.empty() || request_path[0] != '/'
      || request_path.find("..") != std::string::npos)
  {
    rep = reply::stock_reply(reply::bad_request);
    return;
  }

  std::vector<std::string> components;
  split(request_path, components);
  if (components.front()!=ArchiveRoot)
  {
	  // If path ends in slash (i.e. is a directory) then add "index.html".
	  if (request_path[request_path.size() - 1] == '/')
	  {
		  request_path += "index.html";
	  }

	  // Open the file to send back.
	  std::string full_path = doc_root_ + request_path;
	  std::ifstream is(full_path.c_str(), std::ios::in | std::ios::binary);
	  if (!is)
	  {
		  rep = reply::stock_reply(reply::not_found);
		  return;
	  }

	  // Fill out the reply to be sent to the client.
	  rep.status = reply::ok;
	  char buf[512];
	  while (is.read(buf, sizeof(buf)).gcount() > 0)
		rep.content.append(buf, is.gcount());
  }
  else
  {
      if (components.size()==1)
	  {
			request_path+="/index.json";
			serve_directory(rep.content);
			rep.status = reply::ok;
	  }
	  else
	  {
		  VFS* vfs=vfs_;
		  for (size_t i=1; i<components.size(); ++i)
		  {
			  vfs=vfs->lookup(components[i]);
			  if (!vfs)
			  {
				  rep = reply::stock_reply(reply::not_found);
				  return;
			  }
		  }
		  void serve_archive_content(std::string&, void*);
		  serve_archive_content(rep.content, vfs->tag);
		  rep.status = reply::ok;
	  }
  }

  // Determine the file extension.
  std::size_t last_slash_pos = request_path.find_last_of("/");
  std::size_t last_dot_pos = request_path.find_last_of(".");
  std::string extension;
  if (last_dot_pos != std::string::npos && last_dot_pos > last_slash_pos)
  {
	  extension = request_path.substr(last_dot_pos + 1);
  }

  rep.headers.resize(2);
  rep.headers[0].name = "Content-Length";
  rep.headers[0].value = boost::lexical_cast<std::string>(rep.content.size());
  rep.headers[1].name = "Content-Type";
  rep.headers[1].value = mime_types::extension_to_type(extension);
}

bool request_handler::url_decode(const std::string& in, std::string& out)
{
  out.clear();
  out.reserve(in.size());
  for (std::size_t i = 0; i < in.size(); ++i)
  {
    if (in[i] == '%')
    {
      if (i + 3 <= in.size())
      {
        int value;
        std::istringstream is(in.substr(i + 1, 2));
        if (is >> std::hex >> value)
        {
          out += static_cast<char>(value);
          i += 2;
        }
        else
        {
          return false;
        }
      }
      else
      {
        return false;
      }
    }
    else if (in[i] == '+')
    {
      out += ' ';
    }
    else
    {
      out += in[i];
    }
  }
  return true;
}

void test()
{
	request_handler rh("");
	rh.add_virtual_resource("asdf/asdf/ccc.xml", &rh);
	rh.add_virtual_resource("asdf/asdf/ddd.xml", &rh);
	rh.add_virtual_resource("bbb/asdf/ccc.xml", &rh);
	rh.add_virtual_resource("bbb/cc/ccc.xml", &rh);
	rh.add_virtual_resource("cc/ccc.xml", &rh);

	std::string s;
	rh.serve_directory(s);
	puts(s.c_str());
}

} // namespace server
} // namespace http
